package com.wedimob.boxinternetprotection.freebox;

import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;

import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.params.CookiePolicy;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import android.util.Log;

import com.wedimob.html.Form;
import com.wedimob.html.HTMLPage;
import com.wedimob.html.HtmlTools;

public class Freebox extends Observable implements Observer {

    private static final String CLASS_TAG = Freebox.class.getName();
    private static final String LOCAL_URL = "mafreebox.free.fr";
    private static final String OPENDNS_1 = "208.67.222.123";
    private static final String OPENDNS_2 = "208.67.220.123";

    private String url;
    private URL logout;

    private final DefaultHttpClient httpClient = new DefaultHttpClient();
    private HttpContext localContext = new BasicHttpContext();

    private final FreeboxStatus status;
    private final Map<String, FreeboxClient> clients = new HashMap<String, FreeboxClient>();
    private Map<String, String> aliases = new HashMap<String, String>();

    public Freebox(String url) {
        super();
        this.url = url;
        setUpHttpClient();
        status = new FreeboxStatus(this);
        status.addObserver(this);
        Log.v(CLASS_TAG, "Create Freebox with " + url);
    }

    public Freebox() {
        super();
        this.url = LOCAL_URL;
        setUpHttpClient();
        status = new FreeboxStatus(this);
        status.addObserver(this);
        Log.v(CLASS_TAG, "Create Freebox with " + LOCAL_URL);
    }

    private void setUpHttpClient() {
        httpClient.getParams().setParameter(ClientPNames.COOKIE_POLICY,
                CookiePolicy.RFC_2109);
        httpClient.getParams().setParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true);
        int connectionTimeOutSec = 30;
        int socketTimeoutSec = 30;
        HttpConnectionParams.setConnectionTimeout(httpClient.getParams(), connectionTimeOutSec * 1000);
        HttpConnectionParams.setSoTimeout(httpClient.getParams(), socketTimeoutSec * 1000);
    }

    private void checkConnect() {
        if (logout == null)
            throw new RuntimeException("Not connected to freebox");
    }

    private static Map<String, String> createOneParam(String name, String value) {
        Map<String, String> params = new HashMap<String, String>();
        params.put(name, value);
        return params;
    }

    private static String statusAsString(boolean status) {
        return (status ? "true" : "false");
    }

    private static boolean stringAsStatus(String status) {
        return "true".equals(status);
    }

    private Form extractForm(String pageurl, String paramName,
            String paramValue, String formId) {

        checkConnect();

        URL page = HtmlTools.generateURL(url, pageurl,
                createOneParam(paramName, paramValue));
        HTMLPage response = HtmlTools.get(page, httpClient, localContext);
        Iterator<Form> iterForm = HtmlTools.extractForm(response).iterator();
        if (formId == null)
            return iterForm.next();

        while (iterForm.hasNext())
        {
            Form form = iterForm.next();
            if (formId.equals(form.getId()))
                return form;
        }

        throw new RuntimeException("Cannot get form id " + formId);
    }

    public boolean isConnected() {
        boolean result = false;
        try {
            checkConnect();
            result = true;
        } catch (Exception e) {
        }

        Log.v(CLASS_TAG, "isConnected " + result);
        return result;
    }

    public boolean isLocallyConnected() {
        boolean result = false;
        if (isConnected()) {
            result = LOCAL_URL.equals(url);
        }
        Log.v(CLASS_TAG, "isLocallyConnected " + result);
        return result;
    }

    public void connect(String pass) {
        Log.v(CLASS_TAG, "try to connect with " + pass);
        URL loginPage = HtmlTools.generateURL(url, "login.php", null);
        Form form = HtmlTools
                .extractForm(
                        HtmlTools.get(loginPage, httpClient, localContext)).iterator().next();
        form.getField("passwd").setValue(pass);
        HTMLPage response = HtmlTools.post(url, form, httpClient, localContext);
        if (!response.getBody().contains("État de la connexion Internet"))
            throw new RuntimeException("Cannot connect to freebox");

        Document doc = Jsoup.parse(response.getBody());
        Element logoutLink = doc.getElementById("link_login");
        logout = HtmlTools.generateURL(url, logoutLink.attr("href"), null);
        Log.v(CLASS_TAG, "connect is ok");
    }

    public void disconnect() {
        Log.v(CLASS_TAG, "try to disconnect");
        checkConnect();

        HTMLPage response = HtmlTools.get(logout, httpClient, localContext);
        if (!response.getBody().contains("Accès Freebox Server"))
            throw new RuntimeException("Cannot disconnect from freebox");

        httpClient.getCookieStore().clear();
        localContext = null;
        localContext = new BasicHttpContext();
        logout = null;
        clients.clear();
        status.clear();
        Log.v(CLASS_TAG, "disconnect is ok");
    }

    public boolean isWifiEnabled() {
        Log.v(CLASS_TAG, "isWifiEnabled");
        Form form = extractForm("settings.php", "page", "wifi_client", null);
        boolean result = stringAsStatus(form.getField("enabled").getValue());
        return result;
    }

    private void setWifi(boolean status) {
        Form form = extractForm("settings.php", "page", "wifi_client", null);
        form.getField("enabled").setValue(statusAsString(status));
        HtmlTools.post(url, form, httpClient, localContext);
    }

    public void enableWifi() {
        Log.v(CLASS_TAG, "enabledWifi");
        setWifi(true);
    }

    public void disableWifi() {
        Log.v(CLASS_TAG, "disabledWifi");
        setWifi(false);
    }

    public void addtoWhiteList(FreeboxClient client) {
        Log.v(CLASS_TAG, "addtoWhiteList " + client);
        Form form = extractForm("settings.php", "page",
                "wifi_client_filterw", "form_add_mac_filter");
        form.getField("mac").setValue(client.getMac());
        form.getField("comment").setValue(client.getTag());
        HtmlTools.post(url, form, httpClient, localContext);
    }

    public void removeFromWhiteList(FreeboxClient client) {
        Log.v(CLASS_TAG, "removeFromWhiteList " + client);
        try {
            Form form = extractForm("settings.php", "page",
                    "wifi_client_filterw", "form_remove_mac_filter");
            form.getField("mac").setValue(client.getMac());
            HtmlTools.post(url, form, httpClient, localContext);
        } catch (Exception e) {
            Log.e(CLASS_TAG, e.getCause().getMessage());
            throw new RuntimeException("Cannot remove " + client);
        }
    }

    public List<FreeboxClient> getWhiteList() {
        Log.v(CLASS_TAG, "getWhiteList");
        checkConnect();

        URL page = HtmlTools.generateURL(url, "settings.php",
                createOneParam("page",
                        "wifi_client_filterw"));
        HTMLPage response = HtmlTools.get(page, httpClient, localContext);
        Document doc = Jsoup.parse(response.getBody());
        Elements macs = doc.select("form.form_remove_mac_filter");
        List<FreeboxClient> result = new ArrayList<FreeboxClient>();
        for (Element mac : macs) {
            Elements macDesc = mac.select("li");
            result.add(new FreeboxClient(macDesc.get(0).text().replace("Adresse MAC : ", ""), macDesc.get(1).text()
                    .replace("Commentaire : ", "")));
        }
        return result;
    }

    public void addtoBlackList(FreeboxClient client) {
        Log.v(CLASS_TAG, "addtoBlackList " + client);
        Form form = extractForm("settings.php", "page",
                "wifi_client_filterb", "form_add_mac_filter");
        form.getField("mac").setValue(client.getMac());
        form.getField("comment").setValue(client.getTag());
        HtmlTools.post(url, form, httpClient, localContext);
    }

    public void removeFromBlackList(FreeboxClient client) {
        Log.v(CLASS_TAG, "removeFromBlackList " + client);
        try {
            Form form = extractForm("settings.php", "page",
                    "wifi_client_filterb", "form_remove_mac_filter");
            form.getField("mac").setValue(client.getMac());
            HtmlTools.post(url, form, httpClient, localContext);
        } catch (Exception e) {
            Log.e(CLASS_TAG, e.getCause().getMessage());
            throw new RuntimeException("Cannot remove " + client);
        }
    }

    public List<FreeboxClient> getBlackList() {
        Log.v(CLASS_TAG, "getBlackList");
        checkConnect();

        URL page = HtmlTools.generateURL(url, "settings.php",
                createOneParam("page",
                        "wifi_client_filterb"));
        HTMLPage response = HtmlTools.get(page, httpClient, localContext);
        Document doc = Jsoup.parse(response.getBody());
        Elements macs = doc.select("form.form_remove_mac_filter");
        List<FreeboxClient> result = new ArrayList<FreeboxClient>();
        for (Element mac : macs) {
            Elements macDesc = mac.select("li");
            result.add(new FreeboxClient(macDesc.get(0).text().replace("Adresse MAC : ", ""), macDesc.get(1).text()
                    .replace("Commentaire : ", "")));
        }
        return result;
    }

    private static int parseAsSecondes(String str) {
        if ("".equals(str))
            return 0;

        str = str.replace("secondes", "");
        str = str.replace("seconde", "");
        str = str.replace("minutes", ":");
        str = str.replace("minute", ":");
        str = str.replace("heures", ":");
        str = str.replace("heure", ":");
        str = str.replace("jours", ":");
        str = str.replace("jour", ":");
        str = str.replace("mois", ":");
        str = str.replace("années", ":");
        str = str.replace("année", ":");
        str = str.replace(" ", "");
        String elemt[] = str.split(":");
        int result = 0;
        int pow = 0;
        for (int i = elemt.length - 1; i >= 0; i--) {
            if (pow <= 2)
                result += Integer.parseInt(elemt[i]) * Math.pow(60, pow);
            else if (pow == 3)
                result += Integer.parseInt(elemt[i]) * 24 * 60 * 60;
            else if (pow == 4)
                result += Integer.parseInt(elemt[i]) * 30 * 24 * 60 * 60;
            else if (pow == 5)
                result += Integer.parseInt(elemt[i]) * 365 * 30 * 24 * 60 * 60;
            pow++;
        }
        return result;
    }

    private static int extractDigits(String str) {
        if (str == null || "".equals(str))
            return 0;

        int factor = 1;
        if (str.contains("Kio"))
            factor = 1000;
        else if (str.contains("Mio"))
            factor = 1000000;
        else if (str.contains("Gio"))
            factor = 1000000000;

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < str.length(); i++) {
            char ch = str.charAt(i);
            if (ch == '-')
                sb.append(ch);
            if (ch >= '0' && ch <= '9')
                sb.append(ch);
            if (ch == ',' || ch == '.')
                sb.append(".");
        }
        double value = Double.parseDouble(sb.toString());

        return (int) (value * factor);
    }

    public void refreshClients() {
        Log.v(CLASS_TAG, "refreshClients");
        checkConnect();

        URL page = HtmlTools.generateURL(url, "settings.php",
                createOneParam("page",
                        "wifi_client_station"));
        HTMLPage response = HtmlTools.get(page, httpClient, localContext);
        Document doc = Jsoup.parse(response.getBody());
        Element list = doc.getElementById("sta_list");
        Elements macs = list.select("li");
        List<FreeboxClient> updatedClient = new ArrayList<FreeboxClient>();
        for (Element mac : macs) {
            if (!mac.hasAttr("mac"))
                continue;

            Elements eltms = mac.select("li");
            Iterator<Element> iter = eltms.iterator();
            iter.next();
            String macaddr = iter.next().text().replace("Adresse MAC : ", "");
            boolean isAssociated = "associated".equals(iter.next().text().replace("État : ", ""));
            int inactiveTime = parseAsSecondes(iter.next().html().replace("&eacute;", "é")
                    .replace("Temps d'inactivité :", ""));
            int sentDataSize = extractDigits(iter.next().text());
            int recieveDataSize = extractDigits(iter.next().text());
            int signal = extractDigits(iter.next().text());
            if (clients.containsKey(macaddr)) {
                FreeboxClient client = clients.get(macaddr);
                FreeboxClientStatus status = client.getStatus();
                if (status != null) {
                    status.setAssociated(isAssociated);
                    status.setInactiveTime(inactiveTime);
                    status.setRecieveDataSize(recieveDataSize);
                    status.setSentDataSize(sentDataSize);
                    status.setSignal(signal);
                }
            }
            else {
                FreeboxClient client = new FreeboxClient(this, macaddr, getAlias(macaddr), inactiveTime, isAssociated, signal,
                        sentDataSize, recieveDataSize);
                client.addObserver(this);
                clients.put(macaddr, client);
                update(client, client);
            }
            updatedClient.add(clients.get(macaddr));
        }

        // Update other clients
        for (FreeboxClient client : clients.values()) {
            if (!updatedClient.contains(client)) {

                FreeboxClientStatus status = client.getStatus();
                if (status != null) {
                    status.clear();
                }
            }
        }
    }

    public List<FreeboxClient> getClients() {
        Log.v(CLASS_TAG, "getClients");
        checkConnect();

        if (clients.size() == 0)
            refreshClients();

        return new ArrayList<FreeboxClient>(clients.values());
    }

    public boolean isBlockAdsActive() {
        Log.v(CLASS_TAG, "isBlockAdsActive");
        checkConnect();
        Form form = extractForm("settings.php", "page",
                "conn_config", "form_config");
        return stringAsStatus(form.getField("enabled").getValue());
    }

    private void setBlockAds(boolean status) {
        Log.v(CLASS_TAG, "setBlockAds " + status);
        checkConnect();
        Form form = extractForm("settings.php", "page",
                "conn_config", "form_config");
        form.getField("enabled").setValue(statusAsString(status));
        HtmlTools.post(url, form, httpClient, localContext);
    }

    public void enableBlockAds() {
        setBlockAds(true);
    }

    public void disableBlockAds() {
        setBlockAds(false);
    }

    synchronized public void refreshBox() {

        Log.v(CLASS_TAG, "refreshBox");
        checkConnect();

        // status.clear();
        URL page = HtmlTools.generateURL(url, "settings.php",
                createOneParam("page",
                        "conn_status"));
        HTMLPage response = HtmlTools.get(page, httpClient, localContext);
        Document doc = Jsoup.parse(response.getBody());
        Elements statuses = doc.select("div.bloc").first().select("span");
        if (statuses != null)
            for (Element status : statuses) {
                this.status.addProp(status.attr("id"), status.text());
            }

        page = HtmlTools.generateURL(url, "settings.php",
                createOneParam("page",
                        "misc_system"));
        response = HtmlTools.get(page, httpClient, localContext);
        doc = Jsoup.parse(response.getBody());
        statuses = doc.select("div.bloc").first().select("li");
        if (statuses != null)
            for (Element status : statuses) {
                String values[] = status.text().split(" : ");
                this.status.addProp(values[0], values[1]);
            }
    }

    public FreeboxStatus getStatus() {
        Log.v(CLASS_TAG, "getStatus");
        if (status.size() == 0)
            refreshBox();
        return status;
    }

    public boolean isPingActive() {
        Log.v(CLASS_TAG, "isPingActive");
        checkConnect();
        Form form = extractForm("settings.php", "page",
                "conn_config_ping", "form_config");
        return stringAsStatus(form.getField("enabled").getValue());
    }

    private void setPing(boolean status) {
        Log.v(CLASS_TAG, "setPing " + status);
        checkConnect();
        Form form = extractForm("settings.php", "page",
                "conn_config_ping", "form_config");
        form.getField("enabled").setValue(statusAsString(status));
        HtmlTools.post(url, form, httpClient, localContext);
    }

    public void enablePing() {
        setPing(true);
    }

    public void disablePing() {
        setPing(false);
    }

    public boolean isRemoteActive() {
        Log.v(CLASS_TAG, "isRemoteActive");
        checkConnect();
        Form form = extractForm("settings.php", "page",
                "conn_config_remote", "form_config");
        return stringAsStatus(form.getField("http_enabled").getValue());
    }

    private void setRemote(boolean status) {
        Log.v(CLASS_TAG, "setRemoteActive " + status);
        checkConnect();
        Form form = extractForm("settings.php", "page",
                "conn_config_remote", "form_config");
        form.getField("http_enabled").setValue(statusAsString(status));
        HtmlTools.post(url, form, httpClient, localContext);
    }

    public void enableRemote() {
        setRemote(true);
    }

    public void disableRemote() {
        setRemote(false);
    }

    public String getLocalIp() {
        Log.v(CLASS_TAG, "getLocalIp");
        checkConnect();
        Form form = extractForm("settings.php", "page",
                "net", "form_config");
        return form.getField("lan_ip").getValue();
    }

    public String getDistantIp() {
        Log.v(CLASS_TAG, "getDistantIp");
        return getStatus().getProp("conn_ipaddr");
    }

    public ClientFilterType getClientFilterType() {
        Log.v(CLASS_TAG, "getClientFilterType");
        checkConnect();
        Form form = extractForm("settings.php", "page", "wifi_client", null);
        return ClientFilterType.createFromCode(form.getField("mac_filter").getValue());
    }

    public void setClientFilterType(ClientFilterType type) {
        Log.v(CLASS_TAG, "setClientFilterType");
        checkConnect();
        Form form = extractForm("settings.php", "page", "wifi_client", null);
        form.getField("mac_filter").setValue(type.getCode());
        HtmlTools.post(url, form, httpClient, localContext);
    }

    public boolean isAdultContentFilterActive() {
        Log.v(CLASS_TAG, "isAdultContentFilterActive");
        checkConnect();
        Form form = extractForm("settings.php", "page", "net_dhcp", null);
        boolean result = true;
        result = result && !stringAsStatus(form.getField("sticky_assign").getValue());
        result = result && OPENDNS_1.equals(form.getField("dns1").getValue());
        result = result && OPENDNS_2.equals(form.getField("dns2").getValue());
        return result;
    }

    public void enableAdultContentFilter() {
        Log.v(CLASS_TAG, "enableAdultContentFilter");
        checkConnect();
        Form form = extractForm("settings.php", "page", "net_dhcp", null);
        form.getField("sticky_assign").setValue(statusAsString(false));
        form.getField("dns1").setValue(OPENDNS_1);
        form.getField("dns2").setValue(OPENDNS_2);
        HtmlTools.post(url, form, httpClient, localContext);
    }

    public void disableAdultContentFilter() {
        Log.v(CLASS_TAG, "disableAdultContentFilter");
        checkConnect();
        Form form = extractForm("settings.php", "page", "net_dhcp", null);
        form.getField("sticky_assign").setValue(statusAsString(true));
        form.getField("dns1").setValue(getLocalIp());
        form.getField("dns2").setValue("");
        HtmlTools.post(url, form, httpClient, localContext);
    }

    public void reboot() {
        Log.v(CLASS_TAG, "reboot");
        checkConnect();
        Form form = extractForm("settings.php", "page", "misc_system", "form_reboot");
        HtmlTools.post(url, form, httpClient, localContext);
    }

    @Override
    public String toString() {
        return "freebox[" + url + "]";
    }

    @Override
    public void update(Observable o, Object arg) {
        Log.i(CLASS_TAG, "notify observers");
        notifyObservers(arg);
    }

    public void setMacAlias(Map<String, String> aliases) {
        aliases.putAll(aliases);
        
        // Update clients
        for (FreeboxClient client : clients.values()) {
            client.setTag(getAlias(client.getMac()));
        }
    }
    
    private String getAlias(String mac) {
        if(aliases.containsKey(mac))
            return aliases.get(mac);
        
        return mac;
    }
}
